{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "from tqdm import tqdm\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('test.conll.txt') as fopen:\n",
    "    corpus = fopen.read().split('\\n')\n",
    "    \n",
    "with open('dev.conll.txt') as fopen:\n",
    "    corpus_test = fopen.read().split('\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "word2idx = {'PAD': 0,'NUM':1,'UNK':2}\n",
    "tag2idx = {'PAD': 0}\n",
    "char2idx = {'PAD': 0,'NUM':1,'UNK':2}\n",
    "word_idx = 3\n",
    "tag_idx = 1\n",
    "char_idx = 3\n",
    "\n",
    "def process_corpus(corpus, until = None):\n",
    "    global word2idx, tag2idx, char2idx, word_idx, tag_idx, char_idx\n",
    "    sentences, words, depends, labels = [], [], [], []\n",
    "    temp_sentence, temp_word, temp_depend, temp_label = [], [], [], []\n",
    "    for sentence in corpus:\n",
    "        if len(sentence):\n",
    "            sentence = sentence.split('\\t')\n",
    "            for c in sentence[1]:\n",
    "                if c not in char2idx:\n",
    "                    char2idx[c] = char_idx\n",
    "                    char_idx += 1\n",
    "            if sentence[7] not in tag2idx:\n",
    "                tag2idx[sentence[7]] = tag_idx\n",
    "                tag_idx += 1\n",
    "            if sentence[1] not in word2idx:\n",
    "                word2idx[sentence[1]] = word_idx\n",
    "                word_idx += 1\n",
    "            temp_word.append(word2idx[sentence[1]])\n",
    "            temp_depend.append(int(sentence[6]))\n",
    "            temp_label.append(tag2idx[sentence[7]])\n",
    "            temp_sentence.append(sentence[1])\n",
    "        else:\n",
    "            words.append(temp_word)\n",
    "            depends.append(temp_depend)\n",
    "            labels.append(temp_label)\n",
    "            sentences.append(temp_sentence)\n",
    "            temp_word = []\n",
    "            temp_depend = []\n",
    "            temp_label = []\n",
    "            temp_sentence = []\n",
    "    return sentences[:-1], words[:-1], depends[:-1], labels[:-1]\n",
    "        \n",
    "sentences, words, depends, labels = process_corpus(corpus)\n",
    "sentences_test, words_test, depends_test, labels_test = process_corpus(corpus_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from keras.preprocessing.sequence import pad_sequences"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "words = pad_sequences(words,padding='post')\n",
    "depends = pad_sequences(depends,padding='post')\n",
    "labels = pad_sequences(labels,padding='post')\n",
    "\n",
    "words_test = pad_sequences(words_test,padding='post')\n",
    "depends_test = pad_sequences(depends_test,padding='post')\n",
    "labels_test = pad_sequences(labels_test,padding='post')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "idx2word = {idx: tag for tag, idx in word2idx.items()}\n",
    "idx2tag = {i: w for w, i in tag2idx.items()}\n",
    "\n",
    "train_X = words\n",
    "train_Y = labels\n",
    "train_depends = depends\n",
    "\n",
    "test_X = words_test\n",
    "test_Y = labels_test\n",
    "test_depends = depends_test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_char_seq(batch, maxlen_c, maxlen, UNK = 2):\n",
    "    temp = np.zeros((len(batch),maxlen_c,maxlen),dtype=np.int32)\n",
    "    for i in range(len(batch)):\n",
    "        for k in range(len(batch[i])):\n",
    "            for no, c in enumerate(batch[i][k][:maxlen]):\n",
    "                temp[i,k,-1-no] = char2idx.get(c, UNK)\n",
    "    return temp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "maxlen = max(train_X.shape[1], test_X.shape[1])\n",
    "\n",
    "train_X = pad_sequences(train_X,padding='post',maxlen=maxlen)\n",
    "train_Y = pad_sequences(train_Y,padding='post',maxlen=maxlen)\n",
    "train_depends = pad_sequences(train_depends,padding='post',maxlen=maxlen)\n",
    "train_char = generate_char_seq(sentences, maxlen, 30)\n",
    "\n",
    "test_X = pad_sequences(test_X,padding='post',maxlen=maxlen)\n",
    "test_Y = pad_sequences(test_Y,padding='post',maxlen=maxlen)\n",
    "test_depends = pad_sequences(test_depends,padding='post',maxlen=maxlen)\n",
    "test_char = generate_char_seq(sentences_test, maxlen, 30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Attention:\n",
    "    def __init__(self,hidden_size):\n",
    "        self.hidden_size = hidden_size\n",
    "        self.dense_layer = tf.layers.Dense(hidden_size)\n",
    "        self.v = tf.random_normal([hidden_size],mean=0,stddev=1/np.sqrt(hidden_size))\n",
    "        \n",
    "    def score(self, hidden_tensor, encoder_outputs):\n",
    "        energy = tf.nn.tanh(self.dense_layer(tf.concat([hidden_tensor,encoder_outputs],2)))\n",
    "        energy = tf.transpose(energy,[0,2,1])\n",
    "        batch_size = tf.shape(encoder_outputs)[0]\n",
    "        v = tf.expand_dims(tf.tile(tf.expand_dims(self.v,0),[batch_size,1]),1)\n",
    "        energy = tf.matmul(v,energy)\n",
    "        return tf.squeeze(energy,1)\n",
    "    \n",
    "    def __call__(self, hidden, encoder_outputs):\n",
    "        seq_len = tf.shape(encoder_outputs)[1]\n",
    "        batch_size = tf.shape(encoder_outputs)[0]\n",
    "        H = tf.tile(tf.expand_dims(hidden, 1),[1,seq_len,1])\n",
    "        attn_energies = self.score(H,encoder_outputs)\n",
    "        return tf.expand_dims(tf.nn.softmax(attn_energies),1)\n",
    "\n",
    "class Model:\n",
    "    def __init__(\n",
    "        self,\n",
    "        dict_size,\n",
    "        char_dict_size,\n",
    "        size_layers,\n",
    "        learning_rate,\n",
    "        maxlen,\n",
    "        num_blocks = 3,\n",
    "        block_size = 64,\n",
    "    ):\n",
    "        self.word_ids = tf.placeholder(tf.int32, shape = [None, maxlen])\n",
    "        self.labels = tf.placeholder(tf.int32, shape = [None, maxlen])\n",
    "        self.depends = tf.placeholder(tf.int32, shape = [None, maxlen])\n",
    "        self.char_ids = tf.placeholder(tf.int32, shape = [None, maxlen, 30])\n",
    "        embeddings = tf.Variable(tf.random_uniform([dict_size, size_layers], -1, 1))\n",
    "        char_embeddings = tf.Variable(tf.random_uniform([char_dict_size, size_layers], -1, 1))\n",
    "        embedded = tf.nn.embedding_lookup(embeddings, self.word_ids)\n",
    "        char_embedded = tf.nn.embedding_lookup(char_embeddings, self.char_ids)\n",
    "        s_x = tf.shape(char_embedded)\n",
    "        char_embedded = tf.reshape(\n",
    "            char_embedded, shape = [s_x[0] * s_x[1], s_x[-2], size_layers]\n",
    "        )\n",
    "        self.attention = Attention(size_layers)\n",
    "        self.maxlen = tf.shape(self.word_ids)[1]\n",
    "        self.lengths = tf.count_nonzero(self.word_ids, 1)\n",
    "\n",
    "        def residual_block(x, size, rate, block, block_s = block_size, attention = True):\n",
    "            with tf.variable_scope(\n",
    "                'block_%d_%d' % (block, rate), reuse = False\n",
    "            ):\n",
    "                if attention:\n",
    "                    attn_weights = self.attention(tf.reduce_sum(x,axis=1), x)\n",
    "                else:\n",
    "                    attn_weights = x\n",
    "                conv_filter = tf.layers.conv1d(\n",
    "                    attn_weights,\n",
    "                    x.shape[2] // 4,\n",
    "                    kernel_size = size,\n",
    "                    strides = 1,\n",
    "                    padding = 'same',\n",
    "                    dilation_rate = rate,\n",
    "                    activation = tf.nn.tanh,\n",
    "                )\n",
    "                conv_gate = tf.layers.conv1d(\n",
    "                    x,\n",
    "                    x.shape[2] // 4,\n",
    "                    kernel_size = size,\n",
    "                    strides = 1,\n",
    "                    padding = 'same',\n",
    "                    dilation_rate = rate,\n",
    "                    activation = tf.nn.sigmoid,\n",
    "                )\n",
    "                out = tf.multiply(conv_filter, conv_gate)\n",
    "                out = tf.layers.conv1d(\n",
    "                    out,\n",
    "                    block_s,\n",
    "                    kernel_size = 1,\n",
    "                    strides = 1,\n",
    "                    padding = 'same',\n",
    "                    activation = tf.nn.tanh,\n",
    "                )\n",
    "                return tf.add(x, out), out\n",
    "        \n",
    "        forward = tf.layers.conv1d(\n",
    "            char_embedded, block_size, kernel_size = 1, strides = 1, padding = 'SAME'\n",
    "        )\n",
    "        zeros = tf.zeros_like(forward)\n",
    "        for i in range(num_blocks):\n",
    "            for r in [1, 2, 4, 8]:\n",
    "                forward, s = residual_block(\n",
    "                    forward, size = 7, rate = r, block = 10 * (i + 1), attention = False\n",
    "                )\n",
    "                zeros = tf.add(zeros, s)\n",
    "        output = tf.reshape(\n",
    "            tf.reduce_sum(zeros,axis=1), shape = [s_x[0], s_x[1], block_size]\n",
    "        )\n",
    "        forward = tf.layers.conv1d(\n",
    "            embedded, block_size, kernel_size = 1, strides = 1, padding = 'SAME'\n",
    "        )\n",
    "        forward = tf.concat([forward, output], axis = -1)\n",
    "        zeros = tf.zeros_like(forward)\n",
    "        for i in range(num_blocks):\n",
    "            for r in [1, 2, 4, 8, 16]:\n",
    "                forward, s = residual_block(\n",
    "                    forward, size = 7, rate = r, block = i, block_s = block_size * 2, attention = False\n",
    "                )\n",
    "                zeros = tf.add(zeros, s)\n",
    "        logits = tf.layers.conv1d(\n",
    "            zeros, len(idx2tag), kernel_size = 1, strides = 1, padding = 'SAME'\n",
    "        )\n",
    "        logits_depends = tf.layers.conv1d(\n",
    "            zeros, maxlen, kernel_size = 1, strides = 1, padding = 'SAME'\n",
    "        )\n",
    "        log_likelihood, transition_params = tf.contrib.crf.crf_log_likelihood(\n",
    "            logits, self.labels, self.lengths\n",
    "        )\n",
    "        with tf.variable_scope(\"depends\"):\n",
    "            log_likelihood_depends, transition_params_depends = tf.contrib.crf.crf_log_likelihood(\n",
    "                logits_depends, self.depends, self.lengths\n",
    "            )\n",
    "        self.cost = tf.reduce_mean(-log_likelihood) + tf.reduce_mean(-log_likelihood_depends)\n",
    "        self.optimizer = tf.train.AdamOptimizer(\n",
    "            learning_rate = learning_rate\n",
    "        ).minimize(self.cost)\n",
    "        \n",
    "        mask = tf.sequence_mask(self.lengths, maxlen = self.maxlen)\n",
    "        \n",
    "        self.tags_seq, _ = tf.contrib.crf.crf_decode(\n",
    "            logits, transition_params, self.lengths\n",
    "        )\n",
    "        self.tags_seq_depends, _ = tf.contrib.crf.crf_decode(\n",
    "            logits_depends, transition_params_depends, self.lengths\n",
    "        )\n",
    "\n",
    "        self.prediction = tf.boolean_mask(self.tags_seq, mask)\n",
    "        mask_label = tf.boolean_mask(self.labels, mask)\n",
    "        correct_pred = tf.equal(self.prediction, mask_label)\n",
    "        correct_index = tf.cast(correct_pred, tf.float32)\n",
    "        self.accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))\n",
    "        \n",
    "        self.prediction = tf.boolean_mask(self.tags_seq_depends, mask)\n",
    "        mask_label = tf.boolean_mask(self.depends, mask)\n",
    "        correct_pred = tf.equal(self.prediction, mask_label)\n",
    "        correct_index = tf.cast(correct_pred, tf.float32)\n",
    "        self.accuracy_depends = tf.reduce_mean(tf.cast(correct_pred, tf.float32))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.reset_default_graph()\n",
    "sess = tf.InteractiveSession()\n",
    "\n",
    "dim = 128\n",
    "dropout = 1\n",
    "learning_rate = 1e-3\n",
    "batch_size = 8\n",
    "\n",
    "model = Model(len(word2idx), len(char2idx), dim, learning_rate, maxlen)\n",
    "sess.run(tf.global_variables_initializer())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "\n",
    "for e in range(20):\n",
    "    lasttime = time.time()\n",
    "    train_acc, train_loss, test_acc, test_loss, train_acc_depends, test_acc_depends = 0, 0, 0, 0, 0, 0\n",
    "    pbar = tqdm(\n",
    "        range(0, len(train_X), batch_size), desc = 'train minibatch loop'\n",
    "    )\n",
    "    for i in pbar:\n",
    "        batch_x = train_X[i : min(i + batch_size, train_X.shape[0])]\n",
    "        batch_y = train_Y[i : min(i + batch_size, train_X.shape[0])]\n",
    "        batch_char = train_char[i : min(i + batch_size, train_X.shape[0])]\n",
    "        batch_depends = train_depends[i : min(i + batch_size, train_X.shape[0])]\n",
    "        acc_depends, acc, cost, _ = sess.run(\n",
    "            [model.accuracy_depends, model.accuracy, model.cost, model.optimizer],\n",
    "            feed_dict = {\n",
    "                model.word_ids: batch_x,\n",
    "                model.labels: batch_y,\n",
    "                model.char_ids: batch_char,\n",
    "                model.depends: batch_depends\n",
    "            },\n",
    "        )\n",
    "        assert not np.isnan(cost)\n",
    "        train_loss += cost\n",
    "        train_acc += acc\n",
    "        train_acc_depends += acc_depends\n",
    "        pbar.set_postfix(cost = cost, accuracy = acc, accuracy_depends = acc_depends)\n",
    "        \n",
    "    pbar = tqdm(\n",
    "        range(0, len(test_X), batch_size), desc = 'test minibatch loop'\n",
    "    )\n",
    "    for i in pbar:\n",
    "        batch_x = test_X[i : min(i + batch_size, test_X.shape[0])]\n",
    "        batch_char = test_char[i : min(i + batch_size, test_X.shape[0])]\n",
    "        batch_y = test_Y[i : min(i + batch_size, test_X.shape[0])]\n",
    "        batch_depends = test_depends[i : min(i + batch_size, test_X.shape[0])]\n",
    "        acc_depends, acc, cost = sess.run(\n",
    "            [model.accuracy_depends, model.accuracy, model.cost],\n",
    "            feed_dict = {\n",
    "                model.word_ids: batch_x,\n",
    "                model.labels: batch_y,\n",
    "                model.char_ids: batch_char,\n",
    "                model.depends: batch_depends\n",
    "            },\n",
    "        )\n",
    "        assert not np.isnan(cost)\n",
    "        test_loss += cost\n",
    "        test_acc += acc\n",
    "        test_acc_depends += acc_depends\n",
    "        pbar.set_postfix(cost = cost, accuracy = acc, accuracy_depends = acc_depends)\n",
    "    \n",
    "    train_loss /= len(train_X) / batch_size\n",
    "    train_acc /= len(train_X) / batch_size\n",
    "    train_acc_depends /= len(train_X) / batch_size\n",
    "    test_loss /= len(test_X) / batch_size\n",
    "    test_acc /= len(test_X) / batch_size\n",
    "    test_acc_depends /= len(test_X) / batch_size\n",
    "\n",
    "    print('time taken:', time.time() - lasttime)\n",
    "    print(\n",
    "        'epoch: %d, training loss: %f, training acc: %f, training depends: %f, valid loss: %f, valid acc: %f, valid depends: %f\\n'\n",
    "        % (e, train_loss, train_acc, train_acc_depends, test_loss, test_acc, test_acc_depends)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq, deps = sess.run([model.tags_seq, model.tags_seq_depends],\n",
    "        feed_dict={model.word_ids:batch_x[:1]})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq = seq[0]\n",
    "deps = deps[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq[seq>0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_y[0][seq>0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "deps[seq>0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_depends[0][seq>0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
